﻿<ComClass(ClsPoint2d.ClassId, ClsPoint2d.InterfaceId, ClsPoint2d.EventsId)>
Public Class ClsPoint2d

#Region "COM GUID"
    ' 这些 GUID 提供此类的 COM 标识 
    ' 及其 COM 接口。若更改它们，则现有的
    ' 客户端将不再能访问此类。
    Public Const ClassId As String = "426da3a0-49c2-4684-9ea9-253d7b5a6648"
    Public Const InterfaceId As String = "dfd58d7a-3709-4899-87eb-bec796ff1a60"
    Public Const EventsId As String = "98075abd-763b-4cec-8e1c-6ae52662c058"
#End Region

    ' 可创建的 COM 类必须具有一个不带参数的 Public Sub New() 
    ' 否则， 将不会在 
    ' COM 注册表中注册此类，且无法通过
    ' CreateObject 创建此类。
    Public Sub New()
        MyBase.New()
    End Sub

#Region "字段和属性"
    Private _xValue As Double
    Private _yValue As Double

    ''' <summary>
    ''' 平面坐标系的原点
    ''' </summary>
    Public ReadOnly Property Original As ClsPoint2d
        Get
            Return New ClsPoint2d() With {.X = 0, .Y = 0}
        End Get
    End Property
    ''' <summary>
    ''' 横坐标
    ''' </summary>
    ''' <returns></returns>
    Public Property X As Double
        Get
            Return _xValue
        End Get
        Set(value As Double)
            _xValue = value
        End Set
    End Property

    ''' <summary>
    ''' 纵坐标
    ''' </summary>
    ''' <returns></returns>
    Public Property Y As Double
        Get
            Return _yValue
        End Get
        Set(value As Double)
            _yValue = value
        End Set
    End Property

    Public ReadOnly Property Location As 平面坐标系位置

        Get
            Dim m As New ClsMath

            If m.IsZero(Me.X) And m.IsZero(Me.Y) Then
                Return 平面坐标系位置.原点
            Else
                If m.IsZero(Me.X) Then
                    If Me.Y > 0 Then
                        Return 平面坐标系位置.Y正半轴
                    Else
                        Return 平面坐标系位置.Y负半轴
                    End If
                ElseIf m.IsZero(Me.Y) Then
                    If Me.X > 0 Then
                        Return 平面坐标系位置.X正半轴
                    Else
                        Return 平面坐标系位置.X负半轴
                    End If
                Else
                    If Me.X > 0 Then
                        If Me.Y > 0 Then
                            Return 平面坐标系位置.第一象限
                        Else
                            Return 平面坐标系位置.第四象限
                        End If
                    Else
                        If Me.Y > 0 Then
                            Return 平面坐标系位置.第二象限
                        Else
                            Return 平面坐标系位置.第三象限
                        End If
                    End If
                End If
            End If
        End Get

    End Property

    ''' <summary>
    ''' 转字符串格式
    ''' </summary>
    ''' <returns></returns>
    Public ReadOnly Property StringStyle As String
        Get
            Return $"({Me.X},{Me.Y})"
        End Get
    End Property

    Public ReadOnly Property 坐标形式 As String
        Get
            Return $"{Me.X},{Me.Y}"
        End Get
    End Property


#End Region

#Region "构造函数,初始化"

    ''' <summary>
    ''' 构造函数
    ''' </summary>
    ''' <param name="x1">传入的横坐标</param>
    ''' <param name="y1">传入的纵坐标</param>
    Public Sub CreatorByXy(x1 As Double, y1 As Double)
        Me.X = x1
        Me.Y = y1
    End Sub

    ''' <summary>
    ''' 构造函数
    ''' </summary>
    ''' <param name="arr">2个元素的double</param>
    Public Sub CreatorByArray(arr() As Double)
        Me.X = arr(LBound(arr))
        Me.Y = arr(LBound(arr) + 1)
    End Sub

    ''' <summary>
    ''' 构造函数
    ''' </summary>
    ''' <param name="arr">Variant</param>
    Public Sub CreatorByVariant(arr As Object)
        Me.X = arr(LBound(arr))
        Me.Y = arr(LBound(arr) + 1)
    End Sub
    ''' <summary>
    ''' (x,y) 格式的字符串转点对象
    ''' </summary>
    ''' <param name="listPt">(x,y) 格式的字符串</param>
    ''' <param name="split">x与y之间的分割符.默认未逗号</param>
    Public Sub CreatorByString(listPt As String, Optional split As String = ",")
        Dim ptwithspliter = listPt.Trim().Substring(1, listPt.Trim().Length - 2)
        Dim arr = ptwithspliter.Split({split}, StringSplitOptions.RemoveEmptyEntries)
        Me.X = arr(LBound(arr))
        Me.Y = arr(LBound(arr) + 1)
    End Sub

#End Region

#Region "方法"
    Public Function ThreePointsCollinear(ByVal p1 As ClsPoint2d, ByVal p2 As ClsPoint2d) As Boolean
        Return Me.GetVector2dTo(p1).IsParallelTo(Me.GetVector2dTo(p2))
    End Function

    Public Function IsEqualTo(checkPt As ClsPoint2d) As Boolean
        Return Math.Abs(Me.X - checkPt.X) < 0.00000001 And Math.Abs(checkPt.Y - Me.Y) < 0.00000001
    End Function

    'This method returns an array containing the X and Y coordinate values for this point.
    Public Function ToArray() As Double()
        Return {Me.X, Me.Y}
    End Function
    Public Function To3DArray(Optional z As Double = 0) As Double()
        Return {Me.X, Me.Y, z}
    End Function


#End Region

#Region "com不可见"
    Public Overrides Function ToString() As String
        Return $"X={Me.X},Y={Me.Y}"
    End Function
#End Region

#Region "转换操作"

    ''Input point to measure to,Evaluates and returns distance from this point to the point point.
    ''' <summary>
    '''  到点的距离
    ''' </summary>
    ''' <param name="p2d">目标</param>
    ''' <returns></returns>
    Public Function GetDistanceTo(p2d As ClsPoint2d) As Double
        Return Math.Sqrt((Me.X - p2d.X) * (Me.X - p2d.X) + (Me.Y - p2d.Y) * (Me.Y - p2d.Y))
    End Function

    ''' <summary>
    ''' 当前点到直线的距离
    ''' </summary>
    ''' <param name="sp">直线的起点</param>
    ''' <param name="ep">直线的终点</param>
    ''' <returns>距离值</returns>
    Public Function GetDistanceToLine(sp As ClsPoint2d, ep As ClsPoint2d) As Double
        Return Me.GetDistanceTo(Me.GetProjectionPointToLine(sp, ep))
    End Function

    ''' <summary>
    ''' 当前点在直线上的投影
    ''' </summary>
    ''' <param name="sp">直线的起点</param>
    ''' <param name="ep">直线的终点</param>
    ''' <returns>当前点在直线上的投影点的坐标</returns>
    Public Function GetProjectionPointToLine(sp As ClsPoint2d, ep As ClsPoint2d) As ClsPoint2d
        Dim vspep = sp.GetVector2dTo(ep)
        Dim vspme = sp.GetVector2dTo(Me)
        Dim x = vspme.ProjectionOnVector(vspep).Length '当前点在给定直线上的投影长度
        Return sp.Add(vspep.UnitVector.Multiply(x)) '当前点在给定直线上的投影点坐标
    End Function

    ''' <summary>
    ''' 转平面向量
    ''' </summary>
    ''' <returns>以平面坐标系原点为起点,以当前点为终的向量</returns>
    Public Function GetAsVector2d() As ClsVector2d
        Return New ClsVector2d() With {.X = Me.X, .Y = Me.Y}
    End Function

    ''' <summary>
    '''  转平面向量
    ''' </summary>
    ''' <param name="ep">向量的终点</param>
    ''' <returns>以当前点为起点,以当目标传入的点为终的平面向量</returns>
    Public Function GetVector2dTo(ep As ClsPoint2d) As ClsVector2d
        Return New ClsVector2d() With {
            .X = ep.X - Me.X,
            .Y = ep.Y - Me.Y
            }
    End Function
#End Region

#Region "操作符和操作"

    ''' <summary>
    '''当前点加向量生成新的点
    ''' </summary>
    ''' <param name="v">增加的向量</param>
    ''' <returns>新的点</returns>
    Public Function Add(v As ClsVector2d) As ClsPoint2d
        Dim ep As New ClsPoint2d With {
            .X = Me.X + v.X,
            .Y = Me.Y + v.Y
        }
        Return ep
    End Function

    ''' <summary>
    ''' 当前点减去向量生成新的点
    ''' </summary>
    ''' <param name="value">减去向量</param>
    ''' <returns>新的点</returns>
    Public Function Subtract(value As ClsVector2d) As ClsPoint2d
        Dim ep As New ClsPoint2d With {
            .X = Me.X - value.X,
            .Y = Me.Y - value.Y
        }
        Return ep
    End Function

    ''' <summary>
    ''' 旋转当前点
    ''' </summary>
    ''' <param name="angle">旋转角度,弧度制</param>
    ''' <param name="origin">旋转中心</param>
    Public Sub RotateTo(angle As Double, origin As ClsPoint2d)
        Me.X = (Me.X - origin.X) * Math.Cos(angle) - (Me.Y - origin.Y) * Math.Sin(angle) + origin.X
        Me.Y = (Me.X - origin.X) * Math.Sin(angle) + (Me.Y - origin.Y) * Math.Cos(angle) + origin.Y
    End Sub

    ''' <summary>
    ''' 旋转生成新的点
    ''' </summary>
    ''' <param name="angle">旋转角度,弧度制</param>
    ''' <param name="origin">旋转中心</param>
    Public Function Rotate(angle As Double, origin As ClsPoint2d) As ClsPoint2d

        Return New ClsPoint2d With
            {
                 .X = (Me.X - origin.X) * Math.Cos(angle) - (Me.Y - origin.Y) * Math.Sin(angle) + origin.X,
                 .Y = (Me.X - origin.X) * Math.Sin(angle) + (Me.Y - origin.Y) * Math.Cos(angle) + origin.Y
            }

    End Function
    ''' <summary>
    ''' 缩放当前点获取新的点 
    ''' </summary>
    ''' <param name="scaleFactor">缩放的系数，大于1表示放大，小于1表示缩小</param>
    ''' <param name="scaleCenter">缩放的基准点</param>
    ''' <returns>新的点 </returns>
    Public Function Scaling(ByVal scaleFactor As Double, ByVal scaleCenter As ClsPoint2d) As ClsPoint2d
        Return scaleCenter.Add(scaleCenter.GetVector2dTo(Me).Multiply(scaleFactor))
    End Function


    Public Function Mirror(ByVal mirrorP1 As ClsPoint2d, ByVal mirrorP2 As ClsPoint2d) As ClsPoint2d
        If Not Me.ThreePointsCollinear(mirrorP1, mirrorP2) Then
            Dim vMirror = mirrorP1.GetVector2dTo(mirrorP2)
            Dim dis = Me.GetDistanceToLine(mirrorP1, mirrorP2)
            Dim v As ClsVector2d = vMirror.PerpendicularVector.UnitVector.Multiply((2 * dis))
            Dim m As New ClsMath
            Select Case m.IsClockWise(Me, mirrorP1, mirrorP2)
                Case 旋转方向.ClockWise
                    Return Me.Add(v.NegateVector)
                Case 旋转方向.CounterClockWise
                    Return Me.Add(v)
            End Select
        End If
        Return Me
    End Function

    ''' <summary>
    ''' 移动当前点
    ''' </summary>
    ''' <param name="Displacement">移动目标点到初始点的向量</param>
    Public Sub MoveTo(Displacement As ClsVector2d)
        Me.X += Displacement.X
        Me.Y += Displacement.Y
    End Sub
    ''' <summary>
    ''' 当前点与给定点的中点
    ''' </summary>
    ''' <param name="b">给定点</param>
    ''' <returns>当前点与给定点的中点</returns>
    Public Function GetMidPointTo(b As ClsPoint2d) As ClsPoint2d
        Return Me.Add(Me.GetVector2dTo(b).Multiply(0.5))
    End Function

    Public Function PolarPoint(angle As Double, distance As Double) As ClsPoint2d
        Return Me.Add(
            New ClsVector2d() With {
            .X = distance * Math.Cos(angle),
            .Y = distance * Math.Sin(angle)}
            )
    End Function

    Public Function ToLwPolyLineVertext(Optional bulgeValue As Double = 0) As ClsLwPolylineVertex
        Return New ClsLwPolylineVertex With {.Position = Me, .Bulge = bulgeValue}
    End Function
#End Region

#Region "与其他图元的位置关系的判定"

    Public Function RelationToLine(sp As ClsPoint2d, ep As ClsPoint2d) As 点与曲线的位置关系
        Dim pp = Me.GetProjectionPointToLine(sp, ep)
        Dim vsPPP = sp.GetVector2dTo(pp)
        Dim vsPeP = sp.GetVector2dTo(ep)
        If vsPPP.IsParallelTo(vsPeP) Then
            If Math.Abs(vsPPP.GetAngleTo(vsPeP) - Math.PI) < 0.00000001 Then
                Return 点与曲线的位置关系.在曲线段内部
            Else
                Return 点与曲线的位置关系.在曲线段延长线上
            End If
        Else
            If vsPPP.CrossProductWith(vsPeP) < 0 Then
                Return 点与曲线的位置关系.在曲线段的右侧
            Else
                Return 点与曲线的位置关系.在曲线段的左侧
            End If
        End If
    End Function

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="p"></param>
    ''' <param name="tri"></param>
    ''' <returns></returns>
    Public Function PointRelation2Triangle(p As ClsPoint2d, tri As ClsTriangle) As 点与封闭图形的位置关系
        ' 该算法和算法2类似， 可以看作是对算法2的简化， 也是用到向量的叉乘。假设三角形的三个点按照顺时针（或者逆时针）顺序是A,B,C。对于某一点P，求出三个向量PA,PB,PC, 然后计算以下三个叉乘（^表示叉乘符号）

        't1 = PA ^ PB,

        't2 = PB ^ PC,

        't3 = PC ^ PA,

        '如果t1， t2， t3同号（同正或同负）， 那么P在三角形内部， 否则在外部。

        '看是否在三条边上不
        If p.RelationToLine(tri.PointA, tri.PointB) = 点与曲线的位置关系.在曲线段内部 Or
            p.RelationToLine(tri.PointB, tri.PointC) = 点与曲线的位置关系.在曲线段内部 Or
            p.RelationToLine(tri.PointC, tri.PointA) = 点与曲线的位置关系.在曲线段内部 Then
            Return 点与封闭图形的位置关系.在边界上
        Else

            Dim t1 = p.GetVector2dTo(tri.PointA).DotProductWith(p.GetVector2dTo(tri.PointB))
            Dim t2 = p.GetVector2dTo(tri.PointB).DotProductWith(p.GetVector2dTo(tri.PointC))
            Dim t3 = p.GetVector2dTo(tri.PointC).DotProductWith(p.GetVector2dTo(tri.PointA))
            If t1 * t2 > 0 And t1 * t3 > 0 Then
                Return 点与封闭图形的位置关系.在内部
            Else
                Return 点与封闭图形的位置关系.在外部
            End If

        End If
    End Function

    ''' <summary>
    ''' 与矩形的位置关系
    ''' </summary>
    ''' <param name="rec"></param>
    ''' <returns></returns>
    Public Function PointRelation2Rectangle(rec As ClsRectangle) As 点与封闭图形的位置关系
        Dim p = Me
        If p.RelationToLine(rec.RightBtm, rec.RightTop) = 点与曲线的位置关系.在曲线段内部 Or
            p.RelationToLine(rec.RightTop, rec.LeftTop) = 点与曲线的位置关系.在曲线段内部 Or
            p.RelationToLine(rec.LeftBtm, rec.LeftTop) = 点与曲线的位置关系.在曲线段内部 Or
            p.RelationToLine(rec.RightBtm, rec.LeftBtm) = 点与曲线的位置关系.在曲线段内部 Then
            Return 点与封闭图形的位置关系.在边界上
        Else
            If (p.RelationToLine(rec.RightBtm, rec.RightTop) = 点与曲线的位置关系.在曲线段的右侧 And
            p.RelationToLine(rec.RightTop, rec.LeftTop) = 点与曲线的位置关系.在曲线段的右侧 And
            p.RelationToLine(rec.LeftTop, rec.LeftBtm) = 点与曲线的位置关系.在曲线段的右侧 And
            p.RelationToLine(rec.LeftBtm, rec.RightBtm) = 点与曲线的位置关系.在曲线段的右侧) Or
            (p.RelationToLine(rec.RightBtm, rec.RightTop) = 点与曲线的位置关系.在曲线段的左侧 And
            p.RelationToLine(rec.RightTop, rec.LeftTop) = 点与曲线的位置关系.在曲线段的左侧 And
            p.RelationToLine(rec.LeftTop, rec.LeftBtm) = 点与曲线的位置关系.在曲线段的左侧 And
            p.RelationToLine(rec.LeftBtm, rec.RightBtm) = 点与曲线的位置关系.在曲线段的左侧) Then
                Return 点与封闭图形的位置关系.在内部
            Else
                Return 点与封闭图形的位置关系.在外部
            End If
        End If
    End Function

    ''' <summary>
    ''' 点与圆的位置关系
    ''' </summary>
    ''' <param name="cir"></param>
    ''' <returns></returns>
    Public Function PointRelation2Circle(cir As ClsCircle) As 点与封闭图形的位置关系
        Dim dis = Me.GetDistanceTo(cir.Center)
        Select Case dis
            Case Is > cir.Radius
                Return 点与封闭图形的位置关系.在外部
            Case Is < cir.Radius
                Return 点与封闭图形的位置关系.在内部
            Case Else
                Return 点与封闭图形的位置关系.在边界上
        End Select
    End Function

    ''' <summary>
    ''' 点与圆弧的位置关系
    ''' </summary>
    ''' <param name="arc"></param>
    ''' <returns></returns> 
    Public Function PointRelation2Arc(arc As ClsArc) As 点与曲线的位置关系
        Dim cir = arc.ToCircle()
        Select Case Me.PointRelation2Circle(cir)

            Case 点与封闭图形的位置关系.在边界上
                '在圆弧或者其延长线上
                Dim vceme = arc.Center.GetVector2dTo(Me)
                If vceme.AngelToXAxis >= IIf(arc.IsClockWise, arc.EndAngle, arc.StartAngle) And vceme.AngelToXAxis <= IIf(arc.IsClockWise, arc.StartAngle, arc.EndAngle) Then
                    Return 点与曲线的位置关系.在曲线段内部
                Else
                    Return 点与曲线的位置关系.在曲线段延长线上
                End If
            Case 点与封闭图形的位置关系.在外部
                '子圆弧左侧还是右侧决定与圆弧的旋转方向
                Return IIf(arc.IsClockWise, 点与曲线的位置关系.在曲线段的左侧, 点与曲线的位置关系.在曲线段的右侧)
            Case Else
                '子圆弧左侧还是右侧决定与圆弧的旋转方向
                Return IIf(arc.IsClockWise, 点与曲线的位置关系.在曲线段的右侧, 点与曲线的位置关系.在曲线段的左侧)
        End Select
    End Function

#End Region

#Region "与cad转化"

    Public Function CreateFromAcadObject(obj As Object) As Boolean
        If TypeName(obj) <> "IAcadPoint" Then
            Return False
        Else
            Call CreatorByArray(obj.Coordinates)
            Return True
        End If
    End Function
#End Region

End Class


